// Copyright (c) Symbian Ltd 2008. All rights reserved.

// INCLUDE FILES

#include "BluetoothResponder.h"
#include "BluetoothServiceAdvertiser.h"
#include "BluetoothSocketWriterReader.h"
#include "common.hrh"

const TInt KQueueSize = 1;

/*
============================================================================
CBluetoothResponder's two stage constructor
============================================================================
*/
CBluetoothResponder* CBluetoothResponder::NewL(MBluetoothObserver& aRespObs, RSocketServ& aSocketServer)
	{
    CBluetoothResponder* self = new (ELeave) CBluetoothResponder(aRespObs, aSocketServer);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop();
    return self;
	}

/*
============================================================================
CBluetoothResponder's second phase constructor 
============================================================================
*/
void CBluetoothResponder::ConstructL()
	{
	iServiceAdvertiser = new(ELeave) CBluetoothServiceAdvertiser(*this);
	iBtSocketReader = new(ELeave) CSocketReader(iAcceptingSocket, *this);
	iBtSocketWriter = new(ELeave) CSocketWriter(iAcceptingSocket, *this);	
	}

/*
============================================================================
CBluetoothResponder's constructor 
============================================================================
*/
CBluetoothResponder::CBluetoothResponder(MBluetoothObserver& aRespObs, RSocketServ& aSocketServer):
 CBluetoothConnectionBase(EFalse), iRespObs(aRespObs), iSocketServer(aSocketServer), iState(EIdle)
	{
	TUid settingsUID;
	settingsUID.iUid = KRPS_BTServiceID;
	iBtSecurity.SetUid(settingsUID);
	iBtSecurity.SetAuthentication(EFalse);
	iBtSecurity.SetAuthorisation(EFalse);
	iBtSecurity.SetEncryption(EFalse);
	}

/*
============================================================================
CBluetoothResponder's destructor
============================================================================
*/
CBluetoothResponder::~CBluetoothResponder()
	{
	Cancel();
	delete iServiceAdvertiser;
	delete iBtSocketReader;
	delete iBtSocketWriter;
	iAcceptingSocket.Close();	
	iListeningSocket.Close();
	}

/*
============================================================================
DoCancel is called as part of the active object's Cancel().
Cancels all outstanding BT socket operations
============================================================================
*/
void CBluetoothResponder::DoCancel()
	{
	iListeningSocket.CancelAll();
	iAcceptingSocket.CancelAll();
	if(iBtSocketReader->IsActive())
		{		
		iBtSocketReader->Cancel();
		}
	if(iBtSocketWriter->IsActive())
		{		
		iBtSocketWriter->Cancel();
		}
	}

/*
============================================================================
Entry point of the CBluetoothResponder's state machine. The initial state must be EIdle.
A state other than EIdle will result in a KErrInUse. This is to prevent StartL() calling
more than once if the state machine is already active.
============================================================================
*/
void CBluetoothResponder::StartL()
	{
	if (iState != EIdle)
		{
		User::Leave(KErrInUse);
		}
		
	TRAPD(err,FindAvailablePortL());
	if(err!=KErrNone)
		{
		iListeningSocket.Close();
		User::Leave(err);	
		}
	}

/*
============================================================================
Handles CBluetoothResponder's state machine completion events 
============================================================================
*/
void CBluetoothResponder::RunL()
	{
	User::LeaveIfError(iStatus.Int());//The error is handled in the RunError
	
    switch (iState)
        {
        case EIdle:
        	//State machine entry point
        	break;
        case EFindingAvailablePort:
        	//Founding available port completed, start binding the listening socket to the port
        	BindToPortL();
            break;   
        case EBinding:
        	//Binding completed, start advertising the service
        	iServiceAdvertiser->StartL(iPort);
        	iState = EAdvertising;
            break;   
        case EAdvertising:
        	//Advertising completed, start setting BT security
        	RegisterService();
            break;   
        case ERegisteringService:
        	//Setting BT security completed, start listening for incoming connection 
        	ListenL();
            break;   
        case EListening:
        	//Remote device connected, start listening for incoming data
        	iRespObs.ConnectionErr(iHandle, KErrNone);
        case EWaitingForData:
        	//Incoming data received, start listening for incoming data
        case ESendingData:
        	//Sending data completed, start listening for incoming data
        	WaitForData();
            break;
        default:
            User::Leave(KErrCorrupt);
            break;
        };	
	}


/*
============================================================================
Opens a socket using the RFCOMM protocol and ask for an available RFCOMM port.
============================================================================
*/
void CBluetoothResponder::FindAvailablePortL()
	{
	iState = EFindingAvailablePort;
	User::LeaveIfError(iListeningSocket.Open(iSocketServer, KBTRFCOMM));
	/*
	============================================================================
	Get RFCOMM available port if any. Note that you can also try to get any available port manually.
	You can try to bind the listening socket to the current value of iPort (KPort).
	If this fails, increment the port until you exhaust all the ports (KMaxTUint8) or find an available one.
	============================================================================
	*/
	User::LeaveIfError(iListeningSocket.GetOpt(KRFCOMMGetAvailableServerChannel, KSolBtRFCOMM, iPort));
	SelfComplete();
	}

/*
============================================================================
Attempts to bind the listening socket to the BT device address and to an available RFCOMM port and start listening for
incoming remote device connection (Master connection)
============================================================================
*/
void CBluetoothResponder::BindToPortL()
	{
	iState = EBinding;
	iBtSocketAddr.SetPort(iPort);
	User::LeaveIfError(iListeningSocket.Bind(iBtSocketAddr));
	User::LeaveIfError(iListeningSocket.Listen(KQueueSize));	
	SelfComplete();
	}

/*
============================================================================
Attaches the BT security settings to the listening socket
============================================================================
*/
void CBluetoothResponder::RegisterService()
	{
	iBtSocketAddr.SetSecurity(iBtSecurity);
	iState = ERegisteringService;
	SelfComplete();
	}

/*
============================================================================
Sets up the BT socket to listen for incoming connection (Master connection)
============================================================================
*/
void CBluetoothResponder::ListenL()
	{
	iState = EListening;
	/*
	============================================================================
	Closes old accepted socket if open
	============================================================================
	*/
	iAcceptingSocket.Close();

	/*
	============================================================================
	Opens a blank socket
	============================================================================
	*/
	User::LeaveIfError(iAcceptingSocket.Open(iSocketServer));

	/*
	============================================================================
	Waits for incoming connection. The RunL will handle it.
	============================================================================
	*/
	iListeningSocket.Accept(iAcceptingSocket, iStatus);
	SetActive();
	}

/*
============================================================================
Sets the state machine in the listening mode for remote device's incoming data.
Listening is done with CSocketReader (a separate active object). On completion CSocketReader calls the callback
CBluetoothResponder::ReportData either with the data from the remote device or with an error.
============================================================================
*/		
void CBluetoothResponder::WaitForData()
	{
	if(!iBtSocketReader->IsActive())
		{			
		iBtSocketReader->ReadData();
		iState=EWaitingForData;
		}
	}

/*
============================================================================
Sends the data to the remote device (Master). RunL will handle the sending data completion
============================================================================
*/		
void CBluetoothResponder::SendData(const TDesC8& aData)
	{
	//If CSocketWriter is already active we discard the data to send. RPS doesn't need to queue events.
	//You can change CSocketWriter's implementation to support queuing of events if your multiplayer game needs to. 
	if(!iBtSocketWriter->IsActive())
		{			
		iBtSocketWriter->Write(aData);
		iState=ESendingData;
		}
	}
	

/*
============================================================================
Callback from CBluetoothServiceAdvertiser if an error occurs during service advertising. 
============================================================================
*/
void CBluetoothResponder::ReportAdvertiserErr(TInt aError)
	{
	//Informs the observer of the error
	iRespObs.ConnectionErr(iHandle, aError);			
	}

/*
============================================================================
Callback from CBluetoothServiceAdvertiser when the advertising of the service is succesfully completed
============================================================================
*/
void CBluetoothResponder::AdvertiserComplete()
	{
	//Triggers the next step of the state machine (ERegisteringService)
	SelfComplete();	
	}
	
/*
============================================================================
Callback from CSocketWriter on sending data completion
============================================================================
*/
void CBluetoothResponder::WriteComplete(TInt aError)
	{
	if(aError == KErrNone)
		{
		//Informs the observer of sending data completion and trigger the next step of the state machine (EWaitingForData)
		iRespObs.SendDataComplete(iHandle);		
		SelfComplete();
		}
	else
		{
	 	//informs the observer of the error and reset the state machine to the EIdle state			
		Cancel();
		iState = EIdle;
		iRespObs.ConnectionErr(iHandle, aError);
		}
	}

/*
============================================================================
Callback from CSocketReader on receiving data completion
============================================================================
*/
void CBluetoothResponder::ReportData(const TDesC8& aData, TInt aError)
	{		
	if(aError == KErrNone)
		{
		//Passes to the observer the data received from the remote device and trigger the next step
		//of the state machine (EWaitingForData)
		iRespObs.DataReceived(iHandle, aData);
		SelfComplete();
		}
	else
		{			
		//Informs the observer of the error and reset the state machine to the EIdle state			
		Cancel();
		iState = EIdle;
		iRespObs.ConnectionErr(iHandle, aError);
		}
	}

/*
============================================================================
Calls User::RequestComplete on this active object. Used to trigger the next step of the state machine.
============================================================================
*/
void CBluetoothResponder::SelfComplete()
	{
	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();			
	}

/*
============================================================================
Handles a leave occurring in the request completion event handler RunL().
Close all active BT sockets, reset the state machine to the EIdle state and
report the error to the observer
============================================================================
*/
TInt CBluetoothResponder::RunError(TInt aError)
	{
	//Informs the observer of the error and reset the state machine.	
	iAcceptingSocket.Close();
	iListeningSocket.Close();
	iRespObs.ConnectionErr(iHandle, aError);
	iState = EIdle;
	return KErrNone;
	}
